package com.lwt.aspect;

import com.lwt.anno.RedisForString;
import com.lwt.anno.RedisKey;
import com.lwt.common.AnnoUtils;
import com.lwt.exception.ERedisException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.PriorityOrdered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Parameter;
import java.util.concurrent.TimeUnit;

/**
 * 代理标有com.lwt.anno.RedisForString注解的类，根据RedisForString注解设置的key或在方法参数的RedisKey注解查询，
 * 如果查询不到，则执行方法，将方法执行返回值存入redis中
 * redis中查询到值，则直接返回查询到的值
 * 优先读取配置在QueryCache中的key属性为查询redis的key
 * 未配置QueryCache的key时，key为标有RedisKey注解的属性值
 * 返回值为null时，不存入缓存
 * redis的key可以指定前后缀
 *
 * @author luwentao
 */
@Aspect
@Component
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
@ConditionalOnClass(RedisForString.class)
public class RedisForStringAspect implements PriorityOrdered {

    @Autowired
    private RedisTemplate redisTemplate;

    private final Logger logger = LoggerFactory.getLogger(RedisForStringAspect.class);

    @Around(value = "@annotation(com.lwt.anno.RedisForString)")
    public Object around(ProceedingJoinPoint joinPoint) throws ERedisException {
        Parameter[] parameters = AnnoUtils.getParam(joinPoint);
        //redis存入的key
        String key = null;
        //获取注解的方法
        RedisForString redisForString = AnnoUtils.getAnno(joinPoint, RedisForString.class);
        //获得定义的key
        key = redisForString.key();
        if (StringUtils.isEmpty(key)) {
            int redisKeyNum = 0;
            //获取参数值
            for (int i = 0; i < parameters.length; i++) {
                RedisKey redisKey = parameters[i].getAnnotation(RedisKey.class);
                if (redisKey != null) {
                    redisKeyNum++;
                    Object arg = joinPoint.getArgs()[i];
                    if (arg instanceof String) {
                        key = (String) arg;
                    } else {
                        throw new ERedisException("请指定key为字符串类型！");
                    }
                }
            }
            if (redisKeyNum > 1) {
                throw new ERedisException("注解@RedisKey只能定义在一个参数上,而方法参数中定义了" + redisKeyNum + "个");
            }
        }

        if (StringUtils.isEmpty(key)) {
            throw new ERedisException("请指定key值！");
        }
        Object obj = null;
        //查询redis
        key = this.processKey(redisForString, key);
        obj = redisTemplate.opsForValue().get(key);
        if (obj != null) {
            //查询有值，表示redis中有缓存，直接返回
            System.out.println("查询缓存成功，缓存值为：" + obj.toString());
            return obj;
        } else {
            //查询无值，执行方法获得值，再存入redis
            try {
                System.out.println("代理方法执行...");
                obj = joinPoint.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            if (obj != null) {
                // 过期时间
                long expire = redisForString.expire();
                // 过期时间单位，默认秒
                TimeUnit timeUnit = redisForString.TIME_UNIT();
                System.out.println("方法执行结果： value：" + obj);
                System.out.println("缓存添加key：" + key + ",value:" + obj);
                if (expire == 0) {
                    // 不设置过期时间
                    redisTemplate.opsForValue().set(key, obj);
                } else if (expire < 0) {
                    throw new ERedisException("设置的过期时间必须为正整数！");
                } else {
                    // 设置过期时间
                    redisTemplate.opsForValue().set(key, obj, expire, timeUnit);
                }
            }
        }
        return obj;
    }

    private String processKey(RedisForString redisForString, String key) {
        String prefix = redisForString.prefix();
        String suffix = redisForString.suffix();
        return prefix + key + suffix;
    }

    @Override
    public int getOrder() {
        return Integer.MAX_VALUE;
    }
}
